﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using AppEnvironment.Lib;
using AppEnvironment.Lib.Interfaces;

namespace AppEnvironment.Lib.Internal
{
    internal class StateManager
    {
        private class StateHolder 
        {
            public DateTime LastAccess { get; set; }
            public TimeSpan TimeToLive { get; set; }
            public object State { get; set; }
        }

        private static void SetSessionState(object value)
        {
            TimeSpan timeToLive = new TimeSpan(1, 0, 0);
            SortedList<string, object> list = GetSessionList();
            object objValue;
            StateHolder holder;
            if (list.TryGetValue(value.GetType().FullName, out objValue))
            {
                holder = objValue as StateHolder;
            }
            else
            {
                holder = new StateHolder();
                list.Add(value.GetType().FullName, holder);
            }
            holder.State = value;
            holder.LastAccess = DateTime.Now;
            holder.TimeToLive = timeToLive;
        }

        private static void SetApplicationState(object value)
        {
            TimeSpan timeToLive = new TimeSpan(1, 0, 0);
            SortedList<string, object> list = GetApplicationList();
            object objValue;
            StateHolder holder;
            if (list.TryGetValue(value.GetType().FullName, out objValue))
            {
                holder = objValue as StateHolder;
            }
            else
            {
                holder = new StateHolder();
                list[value.GetType().FullName] = holder;
            }
            holder.State = value;
            holder.LastAccess = DateTime.Now;
            holder.TimeToLive = timeToLive;
        }

        private static object GetSessionState(Type type, Func<object> createInstance)
        {
            SortedList<string, object> list = GetSessionList();
            object objValue;
            if (list.TryGetValue(type.FullName, out objValue))
            {
                StateHolder holder = objValue as StateHolder;
                holder.LastAccess = DateTime.Now;
                //optimise: can sweep and clean old states here
                return holder.State;
            }
            else
            {
                object state = CreateObjInstance(type, createInstance);
                SetSessionState(state);
                return state;
            }
        }
        
        private static void ReleaseSessionState(object instance)
        {
            SortedList<string, object> list = GetSessionList();
            object objValue;
            string name = instance.GetType().FullName;
            if (list.TryGetValue(name, out objValue))
            {
                list.Remove(name);
            }
        }


        private static object GetThreadState(Type type, Func<object> createInstance)
        {
            SortedList<string, object> list = GetThreadList();
            object objValue;
            if (list.TryGetValue(type.FullName, out objValue))
            {
                return objValue;
            }
            else
            {
                object state = CreateObjInstance(type, createInstance);
                list.Add(type.FullName, state);
                return state;
            }
        }

        private static void ReleaseThreadState(object instance)
        {
            SortedList<string, object> list = GetThreadList();
            object objValue;
            Type type = instance.GetType();
            if (list.TryGetValue(type.FullName, out objValue))
            {
                list.Remove(type.FullName);
            }
        }

        private static object GetApplicationState(Type type, Func<object> createInstance)
        {
            SortedList<string, object> list = GetApplicationList();
            object objValue;
            if (list.TryGetValue(type.FullName, out objValue))
            {
                StateHolder holder = objValue as StateHolder;
                holder.LastAccess = DateTime.Now;
                //optimise: can sweep and clean old states here
                return holder.State;
            }
            else
            {
                object state = CreateObjInstance(type, createInstance);
                SetApplicationState(state);
                return state;
            }
        }

        

        private static void ReleaseApplicationState(object instance)
        {
            SortedList<string, object> list = GetApplicationList();
            object objValue;
            string name = instance.GetType().FullName;
            if (list.TryGetValue(name, out objValue))
            {
                list.Remove(name);
            }
        }

        private static SortedList<string, object> GetApplicationList()
        {
            return App.Application.Get("StateManager_State", delegate
            {
                return new SortedList<string, object>();
            });
        }

        private static SortedList<string, object> GetSessionList()
        {
            return App.Session.Get("StateManager_State", delegate
            {
                return new SortedList<string, object>();
            });
        }

        private static SortedList<string, object> GetThreadList()
        {
            return App.ContextItems.Get("StateManager_ThreadContext", delegate
            {
                return new SortedList<string, object>();
            });
        }

        private static void ReleaseState(Type requestedType, object instance)
        {
            object[] attributes = requestedType.GetCustomAttributes(typeof(StatefullAttribute), false);
            if (attributes.Length > 0)
            {
                StatefullAttribute statefull = attributes[0] as StatefullAttribute;
                switch (statefull.StateType)
                {
                    case StateTypeEnum.Thread:
                        StateManager.ReleaseThreadState(instance);
                        break;
                    case StateTypeEnum.Session:
                        StateManager.ReleaseSessionState(instance);
                        break;
                    case StateTypeEnum.Application:
                        StateManager.ReleaseApplicationState(instance);
                        break;
                    default:
                        throw new NotImplementedException();
                }
            }
        }


        internal static object GetInstance(Type typeToCreate, Func<object> createInstance)
        {
            object instance = null;
            
            object[] attributes = typeToCreate.GetCustomAttributes(typeof(StatefullAttribute), false);
            if (attributes.Length > 0)
            {
                StatefullAttribute statefull = attributes[0] as StatefullAttribute;
                switch (statefull.StateType)
                {
                    case StateTypeEnum.Thread:
                        instance = StateManager.GetThreadState(typeToCreate, createInstance);
                        break;
                    case StateTypeEnum.Session:
                        instance = StateManager.GetSessionState(typeToCreate, createInstance);
                        break;
                    case StateTypeEnum.Application:
                        instance = StateManager.GetApplicationState(typeToCreate, createInstance);
                        break;
                    default:
                        throw new NotImplementedException();
                }
            }

            bool hasStatefullAttribute = true;

            if (instance == null)
            {
                instance = createInstance();
                hasStatefullAttribute = false;
            }

            IStateFull stateObj = instance as IStateFull;
            if (stateObj != null)
            {
                stateObj.NewInstanceRequested();
                if (!hasStatefullAttribute)
                {
                    stateObj.OnDisposed += delegate
                    {
                        //do nothing, ensuring class can call OnDisposed
                    };
                }
            }

            return instance;
        }

        private static object CreateObjInstance(Type type, Func<object> createInstance)
        {
            object newInstance = createInstance();
            IStateFull stateObj = newInstance as IStateFull;
            if (stateObj != null)
            {
                stateObj.OnDisposed += delegate
                {
                    StateManager.ReleaseState(type, newInstance);
                };
            }
            return newInstance;
        }
    }
}
